﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;

namespace SharePointClient
{
    /// <summary>
    /// Connection is a caching Class for all SharePoint-Objects and the only creatable Object from "outer Space"
    /// </summary>
    public partial class Connection : IDisposable
    {
        private Dictionary<Guid, Object> byGuid = new Dictionary<Guid, object>();
        private Dictionary<Uri, Site> sites = new Dictionary<Uri, Site>();
        private ICredentials credentials = CredentialCache.DefaultNetworkCredentials;
        private Uri server = null;

        const int BlockSize = 4096;

        /// <summary>
        /// Instances a new cached Connection
        /// </summary>
        public Connection()
        {
        }
        /// <summary>
        /// Instances a new cached Connection
        /// </summary>
        /// <param name="uri">Opens a new Site (with load)</param>
        public Connection(Uri uri)
        {
            server = uri;
            Open(uri).Load();
        }
        public ICredentials Credentials { get { return credentials; } set { credentials = value; } }
        /// <summary>
        /// Instances a new cached Connection
        /// </summary>
        /// <param name="url">Opens a new Site (with load)</param>
        public Connection(string url)
        {
            Site s=Open(url);
            server=s.Uri;
            s.Load();
        }
        /// <summary>
        /// Return first requested Uri
        /// </summary>
        public Uri Uri { get { return server; } }
        public string Url { get { string ret = Uri.ToString(); if (ret.EndsWith("/")) return ret.Substring(0, ret.Length - 1); else return ret; } }
        /// <summary>
        /// Opens a Site
        /// </summary>
        /// <param name="uri">Uri of Site</param>
        /// <returns>Site wanted</returns>
        public Site Open(Uri uri)
        {
            if (sites.ContainsKey(uri))
                return sites[uri];
            else
            {
                if (server == null) server = uri;
                Site site = new Site(this, uri);
                sites.Add(uri, site);
                return site;
            }
                
        }
        /// <summary>
        /// Opens a Site
        /// </summary>
        /// <param name="url">Url (as string)</param>
        /// <returns>Site wanted</returns>
        public Site Open(string url)
        {
            Uri uri=new Uri(url);
            OnSiteLoad(this,new UriEventArgs(uri, BeforeOrAfter.Before));
            Site ret= Open(uri);
            OnSiteLoad(this,new UriEventArgs(uri, BeforeOrAfter.After));
            return ret;
        }

        /// <summary>
        /// Internal cache access
        /// </summary>
        /// <param name="guid">Guid-Object to search for</param>
        /// <returns>SharePointClientObject</returns>
        internal object this[Guid guid]
        {
            get 
            {
                if (OnCacheAccess != null) OnCacheAccess(this, new GuidEventArgs(guid, BeforeOrAfter.After, true, byGuid[guid]));
                return byGuid[guid];
            }
            set 
            {
                if (!byGuid.ContainsKey(guid))
                {
                    if (OnCacheAccess !=null) OnCacheAccess(this, new GuidEventArgs(guid, BeforeOrAfter.After, false, value));
                    byGuid.Add(guid, value);
                }
            }
        }
        internal object this[Uri uri]
        {
            get { return sites[uri]; }
            set { if (!sites.ContainsKey(uri)) sites.Add(uri, (Site) value); }
        }
        /// <summary>
        /// Checks if an object is allready in cache
        /// </summary>
        /// <param name="guid">Guid of object</param>
        /// <returns>true if this object is in cache</returns>
        public bool Cached(Guid guid)
        {
            if (byGuid.ContainsKey(guid))
            {
                if (OnCacheAccess != null) OnCacheAccess(this, new GuidEventArgs(guid, BeforeOrAfter.Before, true, byGuid[guid]));
                return true;
            } else {
                if (OnCacheAccess != null) OnCacheAccess(this, new GuidEventArgs(guid, BeforeOrAfter.Before, false, null));
                return false;
            }
        }
        public bool Cached(Uri uri)
        {
            return sites.ContainsKey(uri);
        }
        public event UriEventHandler OnSiteLoad;
        public event GuidEventHandler OnCacheAccess;
        public event ProgressEventHandler Progress;
        public event FileoperationHandler OnFileoperation;

        internal ProgressEventArgs DoProgress(string op)
        {
            ProgressEventArgs args = new ProgressEventArgs(op, BeforeOrAfter.Before);
            if (Progress!=null) Progress(this, args);
            return args;
        }
        internal void UpdateProgress(ProgressEventArgs args)
        {
            if (Progress!=null) Progress(this, args);
        }
        internal FileoperationEventArgs DoFileoperation(Uri file, bool isDownload, int size)
        {
            FileoperationEventArgs args = new FileoperationEventArgs(file, isDownload, size);
            if (OnFileoperation != null) OnFileoperation(this, args);
            return args;
        }
        internal void UpdateFileoperation(FileoperationEventArgs args)
        {
            if (OnFileoperation != null) OnFileoperation(this, args);
        }
        /// <summary>
        /// Download File from Website or WebDAV
        /// </summary>
        /// <param name="fromUri">Uri of file</param>
        /// <param name="toFilename">local filename</param>
        /// <returns>true if succeeded</returns>
        public bool Download(Uri fromUri, string toFilename)
        {

            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(fromUri);

            req.Credentials = Credentials;
            HttpWebResponse resp = null;
            try
            {
                resp = (HttpWebResponse)req.GetResponse();
            }
            catch (WebException webEx)
            {
                return false;
            }
            FileoperationEventArgs fop = DoFileoperation(fromUri, true, Convert.ToInt32(resp.ContentLength));

            Stream stream = resp.GetResponseStream();
            byte[] buffer = new byte[fop.Size];
            fop.Loaded = 0;
            while (fop.Remains > 0)
            {
                fop.Loaded += stream.Read(buffer, fop.Loaded, Math.Min(fop.Remains, BlockSize));
                UpdateFileoperation(fop);
            }
            stream.Close();
            resp.Close();
            FileStream f = new FileStream(toFilename, FileMode.Create);
            f.Write(buffer, 0, buffer.Length);
            f.Close();


            return fop.Loaded == resp.ContentLength;
        }
        public static string GetContentType(string filename)
        {
            string ctype = (string)Microsoft.Win32.Registry.GetValue("HKEY_CLASSES_ROOT\\." + Path.GetExtension(filename).ToLower(), "Content Type", "");
            if (ctype == null || ctype == "") ctype = "application/binary";
            return ctype;
        }

        public bool Upload(string fromFilename, Uri toUri)
        {
            bool ret = false;
            // If toUri does not include the filename (verified by extension) use the same filename)
            if (System.IO.Path.GetExtension(toUri.ToString()) == "" && (!Uri.TryCreate(toUri, toUri.AbsolutePath + "/" + Path.GetFileName(fromFilename), out toUri))) return false;

            byte[] data = File.ReadAllBytes(fromFilename);
            FileoperationEventArgs fop = DoFileoperation(toUri, false, data.Length);

            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(toUri);
            req.Headers.Add("filename", Uri.EscapeDataString(Path.GetFileName(fromFilename)));
            req.Credentials = Credentials;
            req.ContentType = GetContentType(fromFilename);

            req.ContentLength = data.Length;
            req.Method = "PUT";
            Stream stream = req.GetRequestStream();
            while (fop.Remains > 0)
            {
                stream.Write(data, fop.Loaded, Math.Min(BlockSize, fop.Remains));
                fop.Loaded += Math.Min(BlockSize, fop.Remains);
                UpdateFileoperation(fop);
            }
            stream.Close();
            HttpWebResponse resp;
            try
            {
                resp = (HttpWebResponse)req.GetResponse();
                ret = (resp.StatusCode == HttpStatusCode.Created);
            }
            catch (WebException ex)
            {
                System.Diagnostics.Debug.WriteLine("UploadError (Filenname=" + fromFilename + "): " + ex.Message);
                ret = false;
            }
            return ret;
        }

            
        #region IDisposable Member
        /// <summary>
        /// Clear cache
        /// </summary>
        void IDisposable.Dispose()
        {
            byGuid.Clear(); byGuid = null;
            sites.Clear(); sites = null;
        }

        #endregion
    }
}
